/****************************************************************************
*
*					Functions of class ClusterGraph
*
****************************************************************************/

#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <math.h>

#include "ClusterGraph.h"

// parameter for increasing sepSets while construction (an increase is done once per this number of adding variables) - must be '> 0'!
//  setting to 1 - means resizing sepSets for each addition of its variable
#define stepForIncreasingSepSets		16

/****************************************************************************
*					ClusterGraph::ClusterGraph
*
*	Description:	Standard constructor with initialization of internal variables
*	Parameters:		-
*	Returns:		-
*
****************************************************************************/
ClusterGraph::ClusterGraph (void) {
	this->clear();
}


/****************************************************************************
*					ClusterGraph::~ClusterGraph
*
*	Description:	Standard destructor with removing all dynamic internal variables
*	Parameters:		-
*	Returns:		-
*
****************************************************************************/
ClusterGraph::~ClusterGraph (void) {

}


/****************************************************************************
*					ClusterGraph::initStructure
*
*	Description:	Initialize cluster graph from PGMStruct
*	Parameters:		pgmStruct - description of the PGM (for which to construct Cluster Graph)
*	Returns:		0
*
****************************************************************************/
int ClusterGraph::initStructure (PGMStruct& pgmStruct) {
	// Removing all old structure
	this->clear();
	// Looking for features that consists of one variable but are not present in the PGM
	//std::vector <int> oneVarDomain;
	oneVarDomain.resize(pgmStruct.nVars, -1);
	unsigned int numToAdd = pgmStruct.nVars;
	for (int i = 0; i < (int) pgmStruct.features.size(); i++) {
		if (pgmStruct.features[i].varsIDs.size() == 1) {
			oneVarDomain[pgmStruct.features[i].varsIDs[0]] = i;
			numToAdd--;
		}
	}
	// if there are no domains for some features that consist only of one variable
	if (numToAdd > 0) {
		// creating a domainFeature variable for each such feature
		unsigned int curPos; 
		curPos = pgmStruct.features.size();
		pgmStruct.features.resize(curPos + numToAdd);
		for (unsigned int i = 0; i < oneVarDomain.size(); i++) {
			if (oneVarDomain[i] == -1) {
				pgmStruct.features[curPos].varsIDs.resize(1,i);
				oneVarDomain[i] = curPos;
				curPos++;
			}
		}
	}
	//  by now oneVarDomain contains IDs of domains that contain only one variable
	// Starting to fill domainDescriptions and sepSets
	unsigned int d, j, c;
	unsigned int numDom = pgmStruct.features.size();
	unsigned int sepSetSize = 0, sepSetCur = 0;
	domainDescriptions.resize(numDom);
	for (d = 0; d < numDom; d++) {
		// domainDescription for domains with one variable will be filled on the way
		if (pgmStruct.features[d].varsIDs.size() < 2)
			continue;
		// Creating a sepSet and a connection from domain "i" with all domains (that contain one variable) that enter into this domain
		for (j = 0; j < pgmStruct.features[d].varsIDs.size(); j++) {
			if (sepSetCur >= sepSetSize) {
				//adding some more slots to sepSets
				sepSetSize += stepForIncreasingSepSets;
				sepSets.resize(sepSetSize);
			}
			// adding a new sepSet for variable pgmStruct.features[d].varsIDs[j]
			c = pgmStruct.features[d].varsIDs[j];
			//sepSets[sepSetCur].muOneTwo.resize(pgmStruct.numOfValues[c], (float) 1.0/pgmStruct.numOfValues[c]);
			//sepSets[sepSetCur].muTwoOne.resize(pgmStruct.numOfValues[c], (float) 1.0/pgmStruct.numOfValues[c]);
			sepSets[sepSetCur].muOneTwo.resize(pgmStruct.numOfValues[c], 1.0);
			sepSets[sepSetCur].muTwoOne.resize(pgmStruct.numOfValues[c], 1.0);
			sepSets[sepSetCur].direction = true;
			sepSets[sepSetCur].parOne = oneVarDomain[c];
			sepSets[sepSetCur].parTwo = d;
			sepSets[sepSetCur].commonVar = pgmStruct.features[oneVarDomain[c]].varsIDs[0];
			// adding a neigbor to each of the domains 'd' and 'oneVarDomain[c]'
			domainDescriptions[d].neighbors.push_back(sepSetCur);
			domainDescriptions[oneVarDomain[c]].neighbors.push_back(sepSetCur);
			sepSetCur++;
		}
	}
	// getting sepSets to only filled variables - removing unused slots in the end of the vector
	if (sepSetCur < sepSetSize) {
		sepSets.resize(sepSetCur);
	}
	// Setting beliefs for each domain
	unsigned int numOfElem, curSize, b;
	std::vector <unsigned int> vals;
	int u, uMax;
	float curVal, curSum;
	for (d = 0; d < numDom; d++) {
		// calculating size of belief for the domain 'pgmStruct.features[d].varsIDs'
		numOfElem = 1;
		curSize = pgmStruct.features[d].varsIDs.size();
		for (j = 0; j < curSize; j++) {
			numOfElem *= pgmStruct.numOfValues[pgmStruct.features[d].varsIDs[j]];
		}
		// resizing the belief accordingly
		domainDescriptions[d].belief.resize(numOfElem, 1);
		// going through all possible assignments to the domain variables and setting initial belief values
		vals.resize(curSize,0);
		uMax = (int) curSize - 1;
		b = 0;
		curSum = 0.0;
		do {
			// calculating beliefs on a current set of values
			curVal = pgmStruct.calculate (d,vals);
			// setting factor value
			domainDescriptions[d].belief[b] = exp(curVal);
			curSum += domainDescriptions[d].belief[b];
			b++;
			// moving to the next set of values
			u = uMax;
			moveValues (vals, u, pgmStruct.features[d].varsIDs, pgmStruct.numOfValues);
		} while (u >= 0);
		// Normalizing - belief sum set to be 1
		//for (j = 0; j < domainDescriptions[d].belief.size(); j++)
		//	domainDescriptions[d].belief[j] /= curSum;
	}
	return 0;
}

/****************************************************************************
*					ClusterGraph::clear
*
*	Description:	Removes all variables in object internal variables
*	Parameters:		-
*	Returns:		-
*
****************************************************************************/
void ClusterGraph::clear (void) {
	sepSets.clear();
	domainDescriptions.clear();
	Z = 0.0;
}
